﻿using System;

namespace ThematicClustering.Utils
{
    /// <summary>
    /// Négyzetes Mátrixokat megvalósító osztály.
    /// </summary>
    public class SquareMatrix
    {
        #region Fileds

        /// <summary>
        /// Adatokat tároló tömb.
        /// </summary>
        private double[,] mData;

        #endregion

        #region Properties

        /// <summary>
        /// Visszaadja vagy beállítja a mátrix adott 
        /// sorában és oszlopában lévő értékét.
        /// </summary>
        /// <param name="row">Sorszám</param>
        /// <param name="column">Oszlopszám</param>
        /// <returns></returns>
        public double this[int row, int column]
        {
            set { this.mData[row, column] = value; }
            get { return this.mData[row, column]; }
        }

        /// <summary>
        /// Visszaadja a négyzetes mátrix dimenziószámát.
        /// </summary>
        public int Dimension
        {
            get { return mData.Rank + 1; }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Üres négyzetes mátrix.
        /// </summary>
        /// <param name="dimension">Dimenziószám</param>
        public SquareMatrix(int dimension)
        {
            mData = new double[dimension, dimension];
        }

        /// <summary>
        /// Mátrix feltöltése adott értékkel.
        /// </summary>
        /// <param name="dimension">Dimenziószám</param>
        /// <param name="value">Érték</param>
        public SquareMatrix(int dimension, double value)
        {
            mData = new double[dimension, dimension];
            for (int i = 0; i < dimension; i++)
            {
                for (int j = 0; j < dimension; j++)
                {
                    mData[i, j] = value;
                }
            }
        }

        /// <summary>
        /// Oszlopvektor létrehozása egydimenziós tömbből.
        /// </summary>
        /// <param name="values">Egydimenziós tömb</param>
        /// <param name="dimension">Dimenziószám</param>
        public SquareMatrix(double[] values, int dimension)
        {
            mData = new double[dimension, dimension];

            for (int i = 0; i < dimension; i++)
            {
                mData[i, 0] = values[i];
            }
        }

        /// <summary>
        /// Mátrix létrehozása mátrix tömbből.
        /// </summary>
        /// <param name="values">Négyzetes mátrix tömb</param>
        public SquareMatrix(double[,] values)
        {
            mData = (double[,])values.Clone();
        }

        #endregion

        #region Member Methods

        /// <summary>
        /// Egyenlőség vizsgálat.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            return Equals(this, (SquareMatrix)obj);
        }

        /// <summary>
        /// Egyenlőség vizsgálat.
        /// </summary>
        /// <param name="left">Egyik mátrix.</param>
        /// <param name="right">Másik mátrix.</param>
        /// <returns></returns>
        public static bool Equals(SquareMatrix left, SquareMatrix right)
        {
            if (((object)left) == ((object)right))
            {
                return true;
            }

            if ((((object)left) == null) || (((object)right) == null))
            {
                return false;
            }

            if ((left.Dimension != right.Dimension))
            {
                return false;
            }

            for (int i = 0; i < left.Dimension; i++)
            {
                for (int j = 0; j < left.Dimension; j++)
                {
                    if (left[i, j] != right[i, j])
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Mátrix hasító kód visszaadás
        /// </summary>
        /// <returns>Hasító kód</returns>
        public override int GetHashCode()
        {
            return (2*Dimension);
        }

        /// <summary>
        /// Mátrix másolat készítés.
        /// </summary>
        /// <returns>Másolt mátrix</returns>
        public SquareMatrix Clone()
        {
            SquareMatrix X = new SquareMatrix(Dimension);
            for (int i = 0; i < Dimension; i++)
            {
                for (int j = 0; j < Dimension; j++)
                {
                    X[i, j] = mData[i, j];
                }
            }

            return X;
        }

        /// <summary>
        /// Mátrix adatokat tartalmazó tömb másolása.
        /// </summary>
        /// <returns>Másolt tömb</returns>
        public double[,] CloneDatas()
        {
            return (double[,])this.mData.Clone();
        }

        /// <summary>
        /// Mátrix transzponált.
        /// </summary>
        /// <returns>Transzponált mátrix.</returns>
        public SquareMatrix Transpose()
        {
            SquareMatrix X = new SquareMatrix(Dimension);
            for (int i = 0; i < Dimension; i++)
            {
                for (int j = 0; j < Dimension; j++)
                {
                    X[j, i] = mData[i, j];
                }
            }
            return X;
        }

        /// <summary>
        /// Negálja a mátrixot.
        /// </summary>
        /// <returns>Mátrix negált.</returns>
        public void Negate()
        {
            for (int i = 0; i < Dimension; i++)
            {
                for (int j = 0; j < Dimension; j++)
                {
                    mData[i, j] *= -1;
                }
            }
        }

        /// <summary>
        /// 3x3-as mátrixok inverze.
        /// </summary>
        /// <returns>Inverz.</returns>
        //http://www.dr-lex.be/random/matrix_inv.html
        public SquareMatrix Inverse3x3()
        {
            if (this.Dimension != 3)
                throw new Exception("Méret összeférhetetlenség!");

            double Det = this[0, 0] * (this[1, 1] * this[2, 2] - this[1, 2] * this[2, 1]);
            Det -= this[0, 1] * (this[1, 0] * this[2, 2] - this[1, 2] * this[2, 0]);
            Det += this[0, 2] * (this[1, 0] * this[2, 1] - this[1, 1] * this[2, 0]);

            SquareMatrix Result = new SquareMatrix(3);
            Result[0, 0] = (this[1, 1] * this[2, 2] - this[1, 2] * this[2, 1]) / Det;
            Result[1, 0] = (this[1, 2] * this[2, 0] - this[1, 0] * this[2, 2]) / Det;
            Result[2, 0] = (this[1, 0] * this[2, 1] - this[1, 1] * this[2, 0]) / Det;
            Result[0, 1] = (this[0, 2] * this[2, 1] - this[0, 1] * this[2, 2]) / Det;
            Result[1, 1] = (this[0, 0] * this[2, 2] - this[0, 2] * this[2, 0]) / Det;
            Result[2, 1] = (this[0, 1] * this[2, 0] - this[0, 0] * this[2, 1]) / Det;
            Result[0, 2] = (this[0, 1] * this[1, 2] - this[0, 2] * this[1, 1]) / Det;
            Result[1, 2] = (this[0, 2] * this[1, 0] - this[0, 0] * this[1, 2]) / Det;
            Result[2, 2] = (this[0, 0] * this[1, 1] - this[0, 1] * this[1, 0]) / Det;
            return Result;
        }

        #endregion

        #region Operators

        public static bool operator ==(SquareMatrix left, SquareMatrix right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(SquareMatrix left, SquareMatrix right)
        {
            return !Equals(left, right);
        }

        public static SquareMatrix operator +(SquareMatrix left, SquareMatrix right)
        {
            if(left.Dimension != right.Dimension)
                throw new Exception("Méret összeférhetetlenség!");

            SquareMatrix X = new SquareMatrix(left.Dimension);
            for (int i = 0; i < left.Dimension; i++)
            {
                for (int j = 0; j < left.Dimension; j++)
                {
                    X[i, j] = left[i, j] + right[i, j];
                }
            }
            return X;
        }

        public static SquareMatrix operator -(SquareMatrix left, SquareMatrix right)
        {
            if (left.Dimension != right.Dimension)
                throw new Exception("Méret összeférhetetlenség!");

            SquareMatrix X = new SquareMatrix(left.Dimension);
            for (int i = 0; i < left.Dimension; i++)
            {
                for (int j = 0; j < left.Dimension; j++)
                {
                    X[i, j] = left[i, j] - right[i, j];
                }
            }
            return X;
        }

        public static SquareMatrix operator *(SquareMatrix m, double value)
        {
            SquareMatrix X = new SquareMatrix(m.Dimension);
            for (int i = 0; i < m.Dimension; i++)
            {
                for (int j = 0; j < m.Dimension; j++)
                {
                    X[i, j] = m[i, j] * value;
                }
            }
            return X;
        }

        public static SquareMatrix operator *(double value, SquareMatrix m)
        {
            return m * value;
        }

        public static SquareMatrix operator /(SquareMatrix m, double value)
        {
            return m * (1 / value);
        }

        public static SquareMatrix operator /(double value, SquareMatrix m)
        {
            return m * (1 / value);
        }

        public static SquareMatrix operator *(SquareMatrix left, SquareMatrix right)
        {
            if (left.Dimension != right.Dimension)
                throw new Exception("Méret összeférhetetlenség!");

            int dim = left.Dimension;
            SquareMatrix X = new SquareMatrix(dim);
            for (int i = 0; i < dim; i++)
                for (int k = 0; k < dim; k++)
                    for (int j = 0; j < dim; j++)
                        X[i, j] += left[i, k] * right[k, j];

            return X;
        }

        #endregion

    }
}
